/* usrdef.c
 *
 * Implements the userdefined datatypes hash table.
 *
 */

#include "usrdef.h"
#include "gen.h"
#include "rulefuncs.h"
#include "shared.h"


struct usrdef_t* usr[MAX_USRDEF_HASHKEYS];

long datatype_internal_name = 0; /* Holds a counter of anonymous
				    datatypes to be used for internal
				    naming */

/* ----------------------------------------------------------------
 * Inserts a user defined data type into the usrdef table and
 * returns the position it was inserted in
 * ----------------------------------------------------------------
 */
struct usrdef_t *usrdef_insert(char *lexeme, int line_number)
{
  struct usrdef_t *usrdef;
  int key;

  usrdef = usrdef_new();
  usrdef->line_number = line_number;
  usrdef->name = tolower(lexeme);

  /* Insert the new usrdef in it's proper location */  
  key = makekey(tolower(lexeme), MAX_USRDEF_HASHKEYS);
  usrdef->next = usr[key];
  usr[key] = usrdef;

  return usrdef;
}



/* ------------------------------------------------------------
 * Returns the location of the user defined data type identified
 * by lexeme in the requested usrdef_list or NULL if not found
 * ------------------------------------------------------------
 */
struct usrdef_t *usrdef_lookup(char *lexeme)
{
  int key;
  int found = 0;
  struct usrdef_t* usrdef;

  /* Make a hash key out of the given lexeme */
  key = makekey(tolower(lexeme), MAX_USRDEF_HASHKEYS);
  usrdef = usr[key];

  /* Lookup the lexeme */
  while (found == 0 && usrdef != NULL) {
    if (strcmp(tolower(lexeme), tolower(usrdef->name)) == 0)
      found = 1;
    else
      usrdef = usrdef->next;
  }

  return usrdef;
}



/* ------------------------------------------------------------
 * Returns a pointer to a new usrdef
 * ------------------------------------------------------------
 */
struct usrdef_t *usrdef_new()
{
  struct usrdef_t *u;

  u = (struct usrdef_t *) malloc(sizeof(struct usrdef_t));
  CHECK_MEM_ERROR(u)
  u->name = NULL;
  u->scope = -1;
  u->seen_at_least_once = 0;
  u->size = -1;

  return u;
}



/* ------------------------------------------------------------
 * Returns a pointer to a new usrdef name
 * ------------------------------------------------------------
 */
char *usrdef_new_name()
{
  char *n;

  n = (char *) malloc(MAX_NEW_CHAR_SIZE);
  CHECK_MEM_ERROR(n)
  strcpy(n, USRDEF_NEW_ANONYMOUS_NAME_PREFIX);
  strcat(n, inttostring(datatype_internal_name));
  datatype_internal_name++;

  if ( datatype_internal_name < 0) { /* if we rolled over; bail */
    error(-1, "Maximum number of internal userdefined datatype names exceeded");
  }

  return n;
}


/* ------------------------------------------------------------
 * Initializes the usrdef
 * Adds into it the primitive data types
 * ------------------------------------------------------------
 */
void usrdef_init()
{
  int i;
  struct usrdef_t *u;

  for (i = 0; i < MAX_USRDEF_HASHKEYS; i++)
    usr[i] = NULL;  

  /* All language frontends could have pointers */
  u = usrdef_insert(PRIMITIVE_TYPE_NAME_POINTER, -1);
  u->td = new_type_denoter();
  u->td->type = TYPE_DENOTER_T_IDENTIFIER;
  u->size = gen_primitive_size(PRIMITIVE_TYPE_NAME_POINTER);
}



/* ------------------------------------------------------------
 * Prints the user defined data types table
 * ------------------------------------------------------------
 */
void usrdef_print()
{
  int i;

  for (i = 0; i < MAX_USRDEF_HASHKEYS; i++) {
    if (usr[i] != NULL) {
      struct usrdef_t *temp;

      temp = usr[i];

      while (temp != NULL) {
	printf("Name: %s\n", temp->name);
	printf("Line: %d\n", temp->line_number);
	usrdef_print_type_denoter(temp->td, 0);
	printf("Scope: %d\n", temp->scope);
	printf("Size: %ld\n", temp->size);
	printf("------------------------------\n");
	temp = temp->next;
      }
    }
  }

}



/* ------------------------------------------------------------
 * Prints all information relating to a type_denoter_t
 * ------------------------------------------------------------
 */
void usrdef_print_type_denoter(struct type_denoter_t *td, int numOfTabs)
{
  print_tabs(numOfTabs);
  if (td->type == TYPE_DENOTER_T_IDENTIFIER) {
    printf("Type: primitive\n");
    usrdef_print_basetype(td, numOfTabs+1);
  } else if (td->type == TYPE_DENOTER_T_ARRAY_TYPE) {
    printf("Type: array\n");
    usrdef_print_array_type(td, numOfTabs+1);
  } else if (td->type == TYPE_DENOTER_T_CLASS_TYPE) {
    printf("Type: class\n");
  }
}



/* ------------------------------------------------------------
 * Prints the base type of a type_denoter_t
 * ------------------------------------------------------------
 */
void usrdef_print_basetype(struct type_denoter_t *td, int numOfTabs)
{
  print_tabs(numOfTabs);
  printf("BaseType: %s\n", td->name);
}



/* ------------------------------------------------------------
 * Prints array information for a type_denoter_t
 * ------------------------------------------------------------
 */
void usrdef_print_array_type(struct type_denoter_t *td, int numOfTabs)
{
  int dim = 0;
  struct range_t *r;

  usrdef_print_basetype(td->data.at->td, numOfTabs);
  r = td->data.at->r;

  print_tabs(numOfTabs);
  printf("Dimension %d: min=%d, max=%d\n", dim, r->min->ui, r->max->ui);
  dim++;
}


int compute_sum_of_vdls_in_class (struct variable_declaration_list_t *vdl)
{
    int totSize = 0;
	struct variable_declaration_list_t *temp = vdl;
	while (temp!=NULL)
	{
        int vSize = 0;
        struct type_denoter_t * td = temp->vd->tden;
        struct usrdef_t * ud = usrdef_lookup (td->name);
 
        /* variable type class is being stored as type_identifier in the main
         * tree. Only place to identify the type of variable, if it is class is
         * usrdef */
        if (ud->td->type == TYPE_DENOTER_T_CLASS_TYPE)
            {
            vSize = gen_primitive_size(PRIMITIVE_TYPE_NAME_POINTER);
            }
        else
            {
            vSize = compute_symbol_size(td);
            }

		struct identifier_list_t *tempidl = temp->vd->il;

        while (tempidl!=NULL)
		{
            totSize = totSize + vSize; 
            tempidl = tempidl->next;
		}
		temp = temp->next;
	}

    return totSize;
}

int compute_size_of_class (struct class_block_t * cb)
    {
        int totSize = 0;        
        struct variable_declaration_list_t * vdl = cb->vdl;
        totSize = compute_sum_of_vdls_in_class(vdl);

        return totSize;
    }

int compute_symbol_size(struct type_denoter_t * td)
{
    if (td->type == TYPE_DENOTER_T_ARRAY_TYPE)
        {       
            int prim_size = compute_symbol_size (td->data.at->td);

            struct range_t * r = td->data.at->r;
            int range = (r->max->ui) - (r->min->ui) + 1;

            return (range * prim_size);
        }
    else if (td->type == TYPE_DENOTER_T_IDENTIFIER)
        {
            struct usrdef_t *ud;
            if (td->data.id)
                {
                ud = usrdef_lookup (td->name);
                if (ud->td->type == TYPE_DENOTER_T_IDENTIFIER)
                    return gen_primitive_size (td->data.id);
                else
                    return 0;
                }
        }
    else if (td->type == TYPE_DENOTER_T_CLASS_TYPE)
        {
        return compute_size_of_class(td->data.cl->cb);       
        }

    return -1;
}

/* Computes the sizes of all datatypes in the table */
void usrdef_compute_sizes()
{
  // You need to implement this for project 3 :
  int i;

  for (i = 0; i < MAX_USRDEF_HASHKEYS; i++) 
      {
      if (usr[i] != NULL) 
          {
          struct usrdef_t *temp;

          temp = usr[i];

          while (temp != NULL) 
              {
              struct type_denoter_t *td = temp->td;
              int size = compute_symbol_size(td);
            
              if (size!=-1)
                  temp->size = size;
              
              temp = temp->next;
              }
          }
      }
}
